use hitotp::{Totp, Algorithm, CodeType};

const OPTS: hiopt::Options = hiopt::options!["sha1", "sha256", "sha512", "raw", "base32", "base64", "p:", "d:", "0:", "h", "help", "a:", "s:"];

fn main() {
    let args = unsafe { hiopt::raw_args_from_i8(hictor::args()) };
    let mut it = OPTS.noopt_iter(&args[1..]);

    // cargo totp执行时，"totp"会作为命令行参数传入，需要跳过
    let mut key = "totp";
    while key == "totp" {
        key = match it.next() {
            Some(key) => key,
            None => help(),
        };
    }
    let time = match it.next() {
        Some(time) => time.parse::<i64>(),
        None => Ok(Totp::now()),
    };

    let mut time = match time {
        Ok(time) => time,
        _ => help(),
    };

    let mut totp = Totp::from_base32(key);
    for opt in OPTS.opt_iter(args) {
        let (idx, arg) = match opt {
            Ok((idx, arg)) => (idx, arg),
            Err(err) => {
                println!("wrong option: {err:?}");
                help();
            }
        };
        match idx {
            0 => totp.set_algorithm(Algorithm::Sha1),
            1 => totp.set_algorithm(Algorithm::Sha256),
            2 => totp.set_algorithm(Algorithm::Sha512),
            3 => totp.set_code(CodeType::Raw),
            4 => totp.set_code(CodeType::Base32),
            5 => totp.set_code(CodeType::Base64),
            6 => totp.set_period({ let Ok(v) = arg.unwrap().parse::<u32>() else { help(); }; v }),
            7 => totp.set_digits({ let Ok(v) = arg.unwrap().parse::<u32>() else { help(); }; v }),
            8 => totp.set_t0({ let Ok(v) = arg.unwrap().parse::<i64>() else { help(); }; v }),
            9..=10 => help(),
            11 => { time += { let Ok(v) = arg.unwrap().parse::<i64>() else { help(); }; v}; &mut totp },
            12 => { time -= { let Ok(v) = arg.unwrap().parse::<i64>() else { help(); }; v}; &mut totp },
            _ => unreachable!(),
        };
    }

    let len = totp.get_digits() as usize;
    println!("{:01$}", totp.hash(time), len);
}

fn help() -> ! {
    println!("help: [options] key [time]");
    println!("time: default is now");
    println!("options:");
    println!("--sha1: HMAC-SHA1, default option");
    println!("--sha256: HMAC-SHA256");
    println!("--sha512: HMAC-SHA512");
    println!("--base32: key is encoded with base32, default option");
    println!("--base64: key is encoded with base64");
    println!("--raw: key is not encoded");
    println!("-p period: default is 30");
    println!("-d digits: default is 6");
    println!("-0 t0: default is 0");
    println!("-a delay: time += delay");
    println!("-s delay: time -= delay");
    println!("-h | --help: display help information");
    unsafe { exit(0) };
}

#[link(name = "c")]
extern "C" {
    fn exit(val: i32) -> !;
}
